Skip to content

fix(tooling): raise the re-measure heap ceiling to 6144 and give the job the memory (A1) - #14805

Merged
baozhoutao merged 6 commits into
mainfrom
claude/issue-14569-tsc-heap-ceiling-6144
Sep 4, 2026
Merged

fix(tooling): raise the re-measure heap ceiling to 6144 and give the job the memory (A1)#14805
baozhoutao merged 6 commits into
mainfrom
claude/issue-14569-tsc-heap-ceiling-6144

Conversation

@claude

@claude claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Fixes #14569

Ruling A1 (comment 5528367074, 2026-09-03, director seat, decision batch 22, maintainer 「同意」) completes the earlier ruling A rather than replacing it: raise on a recorded measurement, delivered the only way that measurement allows. The measurement itself was taken in the previous round on this branch and is unchanged; what this round adds is the raise, in the two pieces that make it honest.

The two halves — they ship together or not at all

Half Change
.github/workflows/lint.yml the typecheck-debt job's step "Re-measure the type-check DEBT / TEST_DEBT ledger" now runs under NODE_OPTIONS: --max-old-space-size=6144, so the process running tsc really has the old space the pin describes
scripts/check-type-check-coverage.mjs CI_TSC_HEAP_CEILING_MB 4096 to 6144, plus two remeasureHeapCeiling self-test rows pinning both directions of the pairing

The runner measurement already written beside the constant stays as the evidence. The stale arm is untouched — it still refuses any pin above the process's own limit, and that refusal is exactly what makes the pairing inseparable.

Why a bare constant raise is not shippable, and how this PR proves it

remeasureHeapCeiling chooses the minimum of the pin and the limit the running process actually has, and refuses outright when a CI runner's own limit is below the pin. So with the constant at 6144 and no NODE_OPTIONS, the debt lane exits 1 before the first tsc — on every PR and on main. Both directions were run on this tree, at the commit below, with the exit code captured before any pipe:

# A — the runner as it is WITHOUT the workflow half (its measured 4096 MB default)
$ GITHUB_ACTIONS=true NODE_OPTIONS=--max-old-space-size=4096 \
    node scripts/check-type-check-coverage.mjs --re-measure
::error::scripts/check-type-check-coverage.mjs pins a CI heap ceiling of 6144 MB, but THIS CI
runner's own default is 4144 MB -- the pin is now ABOVE the ceiling it claims to describe [...]
EXIT=1        # refused before the first tsc

# B — the runner as this PR now starts it
$ GITHUB_ACTIONS=true NODE_OPTIONS=--max-old-space-size=6144 \
    node scripts/check-type-check-coverage.mjs --re-measure
  heap: tsc runs under --max-old-space-size=6144 MB -- the CI-shaped ceiling pinned by
  scripts/check-type-check-coverage.mjs; this process's own limit is 6192 MB.
EXIT=3        # past the stale arm; PREREQUISITE NOT MET on an unbuilt closure, which is
              # this gate's NOT-MEASURED code, not a pass and not a red

A is the failure this card exists to avoid; B is the same tree with the workflow half in place. Both are now pinned as self-test rows so the pairing cannot rot silently:

  • "the workflow's own NODE_OPTIONS ties the pin and is not read as a tighter caller cap"heapLimitMb: 6192, caller cap 6144, on CI: chosen ceiling 6144, named as the CI pin (the tie-break keeps the pin's name, which is what the job log prints).
  • "the same runner WITHOUT the workflow NODE_OPTIONS — its 4144 MB default — is refused"heapLimitMb: 4144, on CI: chosen 4144, stale set.

Is the runner measurement still valid?

Yes, and it is re-stated rather than re-taken. It was read first-hand inside the Type Check · debt ledger job on 2026-09-03 (run 33708954003, job 100504131338, image ubuntu24 20260831.293.1, Node v22.23.2, 4 vCPU) and nothing in this round's main merge touches CI's runner image, its Node version, or that job's shape. The two readings the raise rests on:

Cap handed to the heaviest ledger program tsc "Memory used" Peak RSS Check time OOM
--max-old-space-size=4096 4,077,718K 4,212,904 kB 26.84 s no
--max-old-space-size=6144 4,420,706K 4,545,500 kB 21.90 s no

6144 is therefore measured achievable on that runner, not assumed: it was run there. Box capacity is not the scarce resource either — MemTotal 16,373,452 kB with ~918 MB held by the job's other 153 processes, and the lowest MemAvailable seen at any moment of either run was 10,562,192 kB, about 1.7x the 6144 MB the pin now asks for.

One protection the pair costs, recorded rather than discovered later

With NODE_OPTIONS set explicitly on that step, heap_size_limit there reads 6192 whatever the runner's physical memory does. So on that one job the stale arm can no longer notice the runner shrinking — it now only notices a pin above a defaulted process. The margin above is what makes that acceptable, and the constant's comment says so in place, with the remedy if the margin is ever in doubt: a fresh runner measurement and a smaller number in both places, never a bigger one here.

What is in the diff

 .github/workflows/lint.yml            |  26 ++++++++
 scripts/check-type-check-coverage.mjs | 115 +++++++++++++++++++++++++---------

Of the 115 changed lines in the gate script, 16 are non-comment (mechanically counted) and they are exactly: the constant, and the two self-test rows above. Everything else is the comment block that carries the measurement and the reason the two halves are inseparable. The 26 workflow lines are the env: block plus the comment explaining why it cannot be edited alone.

Verification — all of it at a68aed7ec, the final commit

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed; it derives its own change set from the merge base) reports 45 families for this diff: 38 by path, 2 by change kind (this edits a gate script), 6 declared whole-tree, one family reached both ways. All 45 were harvested with --commands and run, every exit code captured with redirect-then-capture, never through a pipe.

  • 44 green. Including check:type-check-coverage, check:pm-dispatch-gates, pm/bare-root-worklist --self-test, check-self-test-wired, check-self-test-workflow-commands, check-step-collectors, check-aggregator-roster, check-required-contexts, check-ci-filter-parity, check-closing-keyword-parity, check:nul-bytes, check:watch-hint-literal, check:shard-attestation, check:stall-guard-budget, check:stall-guard-headroom, check:workflow-status-functions, check:node-version, check:pnpm-acquisition.
  • 1 NOT MEASURED. node scripts/check-test-completeness.mjs exits 3, its own declared "nothing was measured" code — no local vitest summary exists. Not a pass and not a finding.

pnpm check:type-check-debt was RUN, not narrowed — that is the family this diff actually moves, so last round's narrowing no longer applies. Full closure build first (turbo run build --filter='./packages/*' --filter='./packages/*/*', 71/71 tasks, 7m20s), then the gate, both through the shared verify lock:

✓ check:type-check-coverage --self-test — 48 semantic case(s) + 68 observation case(s) +
  45 re-measure case(s) + 28 built-closure case(s) + 19 auto-lowering case(s) +
  18 exit-code case(s) hold.
  heap: tsc runs under --max-old-space-size=6144 MB -- the CI-shaped ceiling pinned by
  scripts/check-type-check-coverage.mjs; this process's own limit is 8240 MB.
check-type-check-coverage --re-measure: OK — 17 ledger entr(ies) re-measured in 138.9s,
  217 raw tsc error(s) total, none above its recorded number.
DEBT_EXIT=0

The re-measure case count moved 43 to 45 — the two new rows above.

The gate script's own suite, owed whether or not the derivation names it. This diff edits scripts/check-type-check-coverage.mjs, a gate script. Its test surface is --self-test (green above, and check-self-test-wired confirms it is wired); a git grep for the script's name across *.test.ts / *.test.mjs finds 12 files, and every hit is a prose mention in a comment — none of them executes the script. There is no vitest pin suite to miss.

ESLint — narrowing, declared, with its three readings. pnpm lint is eslint . --no-inline-config over the whole repo and is CI's to run. Narrowed here to the changed files, and the narrowing is measured rather than assumed: (1) the population is read from ESLint's own config, not guessed — isPathIgnored('.github/workflows/lint.yml') is true, so the workflow half is not in ESLint's population at all, and the .mjs is false, so it is; (2) the file count comes from --format json1 file linted, 0 errors, 0 warnings; (3) config invariance for untouched files — calculateConfigForFile reports parserOptions.project undefined, i.e. type-aware linting is off, so nothing in this diff can move the verdict of a file it does not touch.

Control bytes. grep -naP over the control ranges across both edited files: exit 1, no matches, captured before any pipe.

skip-changeset — already on this PR, and correct: nothing is published from any released package.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

#14569)

Reverted before this PR's final diff. `CI_TSC_HEAP_CEILING_MB` may only move
on a reading taken where the verdict is taken -- the `Type Check · debt ledger`
job on `ubuntu-latest` -- and this container cannot download job logs. Check-run
ANNOTATIONS are readable over REST, so the probe emits its readings as
`::notice` workflow commands from that job:

- the runner's MemTotal/MemAvailable/Swap, image, nproc, and the gate process's
  own V8 `heap_size_limit` (the runner's default old space);
- what else is resident at the point the re-measure starts (`ps` RSS census);
- the `packages/qa/http-conformance` TEST_DEBT program -- the same generated
  project `measureTestDebt` writes -- run with `--extendedDiagnostics` under
  `--max-old-space-size=4096` and under `6144`, reporting tsc's own "Memory
  used", peak RSS, and the machine's minimum MemAvailable during each run.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 3, 2026
…NG_MB

The pin's provenance was archaeology through a failed job's GC trace, which
bracketed the runner's old space into [4040, 4148] MB. It is now a first-hand
reading, taken where the verdict is taken -- inside the `Type Check · debt
ledger` job, by a temporary probe step (reverted in the previous commit) that
emitted its numbers as `::notice` annotations:

  runner        ubuntu24 20260831.293.1, 4 vCPU, MemTotal 16,373,452 kB
                (~15.6 GiB) -- not the 7 GB the finding assumed
  consumers     153 processes / 940,316 kB (~918 MB); the job's steps are
                sequential, so nothing runs beside the re-measure
  this gate     heap_size_limit 4144 MB with NODE_OPTIONS unset -- the runner's
                V8 default, confirming the 4096 MB old space directly
  heaviest      qa/http-conformance's TEST_DEBT program under two caps:
  program       4096 -> 4,077,718K used, 4,212,904 kB peak RSS, 26.84s check
                6144 -> 4,420,706K used, 4,545,500 kB peak RSS, 21.90s check

The pair is the headroom reading the finding asked for: 343 MB more heap keeps
343 MB more live and finishes ~5s sooner, so under 4096 the program is paying
GC pressure to fit. The constant does NOT move on it, and the measurement is
why: the scarce resource is V8's default old space (4096 MB), not the runner's
memory, and this number describes that default exactly.

The comment also records what the measurement made mechanically visible --
raising the pin alone cannot deliver a roomier run. `remeasureHeapCeiling`
minimises over the pin and the running process's own limit, so a 6144 pin under
the runner's default still chooses 4144, and the `stale` arm then refuses the
run outright: `--re-measure` exits 1 before the first tsc. Reproduced against a
4144 MB process. Delivering a raise needs the gate PROCESS given the memory
first, which is a workflow decision and is escalated on #14569.

The self-test row for "a box shaped like CI" gains a note that its `+ 48` is
now the measured runner rather than a construction.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
…b the memory

Ruling A1 (#14569, 2026-09-03). The 02:25Z ruling A — raise on a recorded
measurement — is completed the only way the measurement allows: the pin and
the process's actual old space move together.

- .github/workflows/lint.yml: the `typecheck-debt` job's re-measure step now
  runs under `NODE_OPTIONS: --max-old-space-size=6144`, so the process running
  tsc really has the old space the pin describes. V8's default there is
  4096 MB, measured on the runner.
- CI_TSC_HEAP_CEILING_MB: 4096 -> 6144, with the runner measurement already
  written beside it kept as the evidence.
- Two new `remeasureHeapCeiling` self-test rows pin both directions of the
  pairing: the runner as the workflow now starts it (6192 reported, caller cap
  tying the pin, chosen ceiling 6144 named as the CI pin), and the same runner
  WITHOUT the workflow line (its measured 4144 MB default, refused).

The `stale` arm is untouched and still refuses any pin above the process's own
limit — that refusal is what caught the bare constant raise, and it is what
keeps the two halves inseparable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@github-actions github-actions Bot added size/m and removed size/s labels Sep 3, 2026
@claude claude Bot changed the title fix(tooling): raise the re-measure heap ceiling on a runner measurement, not on a typed number fix(tooling): raise the re-measure heap ceiling to 6144 and give the job the memory (A1) Sep 3, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 4, 2026 00:10
@baozhoutao
baozhoutao enabled auto-merge September 4, 2026 00:10
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 4, 2026
Merged via the queue into main with commit 9221ec8 Sep 4, 2026
35 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14569-tsc-heap-ceiling-6144 branch September 4, 2026 00:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants